<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<meta name="keywords" content="Seata、动态数据源、DataSource、ConcurrentHashMap、computeIfAbsent" />
	<meta name="description" content="本文主要介绍了一个线上问题，因ConcurrentHashMap的Bug而导致的Seata动态数据源代理死锁" />
	<!-- 网页标签标题 -->
	<title>ConcurrentHashMap导致的Seata死锁问题</title>
  <link rel="shortcut icon" href="/img/seata_logo_small.jpeg"/>
	<link rel="stylesheet" href="/build/blogDetail.css" />
</head>
<body>
	<div id="root"><div class="blog-detail-page" data-reactroot=""><header class="header-container header-container-normal"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="//img.alicdn.com/tfs/TB1gqL1w4D1gK0jSZFyXXciOVXa-1497-401.png"/></a><div class="search search-normal"><span class="icon-search"></span></div><span class="language-switch language-switch-normal">En</span><div class="header-menu"><img class="header-menu-toggle" src="https://img.alicdn.com/tfs/TB14eEmw7P2gK0jSZPxXXacQpXa-38-32.png"/><ul><li class="menu-item menu-item-normal"><a href="/zh-cn/index.html" target="_self">首页</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/docs/overview/what-is-seata.html" target="_self">文档</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/docs/developers/developers_dev.html" target="_self">开发者</a></li><li class="menu-item menu-item-normal menu-item-normal-active"><a href="/zh-cn/blog/index.html" target="_self">博客</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/community/index.html" target="_self">社区</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/blog/download.html" target="_self">下载</a></li></ul></div></div></header><section class="blog-content markdown-body"><h1>背景介绍</h1>
<ol>
<li>seata版本：1.4.0，但1.4以下的所有版本也都有这个问题</li>
<li>问题描述：在一个全局事务中，一个分支事务上的纯查询操作突然卡住了，没有任何反馈(日志/异常)，直到消费端RPC超时</li>
</ol>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/03a7f737b56e45b4b74e662033ec74f6~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<h1>问题排查</h1>
<ol>
<li>整个流程在一个全局事务中，消费者和提供者可以看成是全局事务中的两个分支事务，消费者 --&gt; 提供者</li>
<li>消费者先执行本地的一些逻辑，然后向提供者发送RPC请求，确定消费者发出了请求已经并且提供者接到了请求</li>
<li>提供者先打印一条日志，然后执行一条纯查询SQL，如果SQL正常执行会打印日志，但目前的现象是只打印了执行SQL前的那条日志，而没有打印任何SQL相关的日志。找DBA查SQL日志，发现该SQL没有执行</li>
<li>确定了该操作只是全局事务下的一个纯查询操作，在该操作之前，全局事务中的整体流程完全正常</li>
<li>其实到这里现象已经很明显了，不过当时想法没转变过来，一直关注那条查询SQL，总在想就算查询超时等原因也应该抛出异常啊，不应该什么都没有。DBA都找不到查询记录，那是不是说明SQL可能根本就没执行啊，而是在执行SQL前就出问题了，比如代理？</li>
<li>借助arthas的watch命令，一直没有东西输出。第一条日志的输出代表这个方法一定执行了，迟迟没有结果输出说明当前请求卡住了，为什么卡住了呢？</li>
<li>借助arthas的thread命令 <code>thread -b</code> 、<code>thread -n</code>，就是要找出当前最忙的线程。这个效果很好，有一个线程CPU使用率<code>92%</code>,并且因为该线程导致其它20多个Dubbo线程<code>BLOCKED</code>了。堆栈信息如下</li>
<li>分析堆栈信息，已经可以很明显的发现和seata相关的接口了，估计和seata的数据源代理有关；同时发现CPU占用最高的那个线程卡在了<code>ConcurrentHashMap#computeIfAbsent</code>方法中。难道<code>ConcurrentHashMap#computeIfAbsent</code>方法有bug？</li>
<li>到这里，问题的具体原因我们还不知道，但应该和seata的数据源代理有点关系，具体原因我们需要分析业务代码和seata代码</li>
</ol>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/faac0be0982e45a7a43b335e8f8b44bf~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<h1>问题分析</h1>
<h3>ConcurrentHashMap#computeIfAbsent</h3>
<p>这个方法确实有可能出问题：如果两个key的hascode相同，并且在对应的mappingFunction中又进行了computeIfAbsent操作，则会导致死循环，具体分析参考这篇文章：<a href="https://juejin.cn/post/6844904191077384200">https://juejin.cn/post/6844904191077384200</a></p>
<h3>Seata数据源自动代理</h3>
<p>相关内容之前有分析过，我们重点来看看以下几个核心的类：</p>
<ol>
<li>SeataDataSourceBeanPostProcessor</li>
<li>SeataAutoDataSourceProxyAdvice</li>
<li>DataSourceProxyHolder</li>
</ol>
<h5>SeataDataSourceBeanPostProcessor</h5>
<p><code>SeataDataSourceBeanPostProcessor</code>是<code>BeanPostProcessor</code>实现类，在<code>postProcessAfterInitialization</code>方法(即Bean初始化之后)中，会为业务方配置的数据源创建对应的<code>seata代理数据源</code></p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SeataDataSourceBeanPostProcessor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">BeanPostProcessor</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">postProcessAfterInitialization</span><span class="hljs-params">(Object bean, String beanName)</span> <span class="hljs-keyword">throws</span> BeansException </span>{
        <span class="hljs-keyword">if</span> (bean <span class="hljs-keyword">instanceof</span> DataSource) {
            <span class="hljs-comment">//When not in the excludes, put and init proxy.</span>
            <span class="hljs-keyword">if</span> (!excludes.contains(bean.getClass().getName())) {
                <span class="hljs-comment">//Only put and init proxy, not return proxy.</span>
                DataSourceProxyHolder.get().putDataSource((DataSource) bean, dataSourceProxyMode);
            }
            <span class="hljs-comment">//If is SeataDataSourceProxy, return the original data source.</span>
            <span class="hljs-keyword">if</span> (bean <span class="hljs-keyword">instanceof</span> SeataDataSourceProxy) {
                LOGGER.info(<span class="hljs-string">"Unwrap the bean of the data source,"</span> +
                    <span class="hljs-string">" and return the original data source to replace the data source proxy."</span>);
                <span class="hljs-keyword">return</span> ((SeataDataSourceProxy) bean).getTargetDataSource();
            }
        }
        <span class="hljs-keyword">return</span> bean;
    }
}
</code></pre>
<h5>SeataAutoDataSourceProxyAdvice</h5>
<p><code>SeataAutoDataSourceProxyAdvice</code>是一个MethodInterceptor，seata中的<code>SeataAutoDataSourceProxyCreator</code>会针对<code>DataSource类型的Bean</code>创建动态代理对象，代理逻辑就是<code>SeataAutoDataSourceProxyAdvice#invoke</code>逻辑。即：执行<code>数据源AOP代理对象</code>的相关方法时候，会经过其<code>invoke</code>方法，在<code>invoke</code>方法中再根据当原生数据源，找到对应的<code>seata代理数据源</code>，最终达到执行<code>seata代理数据源</code>逻辑的目的</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SeataAutoDataSourceProxyAdvice</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">MethodInterceptor</span>, <span class="hljs-title">IntroductionInfo</span> </span>{
    ......
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span><span class="hljs-params">(MethodInvocation invocation)</span> <span class="hljs-keyword">throws</span> Throwable </span>{
        <span class="hljs-keyword">if</span> (!RootContext.requireGlobalLock() &amp;&amp; dataSourceProxyMode != RootContext.getBranchType()) {
            <span class="hljs-keyword">return</span> invocation.proceed();
        }
        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();
        Method m = BeanUtils.findDeclaredMethod(dataSourceProxyClazz, method.getName(), method.getParameterTypes());
        <span class="hljs-keyword">if</span> (m != <span class="hljs-keyword">null</span>) {
            SeataDataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis(), dataSourceProxyMode);
            <span class="hljs-keyword">return</span> m.invoke(dataSourceProxy, args);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> invocation.proceed();
        }
    }
}
</code></pre>
<h5>DataSourceProxyHolder</h5>
<p>流程上我们已经清楚了，现在还有一个问题，如何维护<code>原生数据源</code>和<code>seata代理数据源</code>之间的关系？通过<code>DataSourceProxyHolder</code>维护，这是一个单例对象，该对象中通过一个ConcurrentHashMap维护两者的关系：<code>原生数据源</code>为key --&gt; <code>seata代理数据源</code> 为value</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataSourceProxyHolder</span> </span>{
      <span class="hljs-function"><span class="hljs-keyword">public</span> SeataDataSourceProxy <span class="hljs-title">putDataSource</span><span class="hljs-params">(DataSource dataSource, BranchType dataSourceProxyMode)</span> </span>{
        DataSource originalDataSource = dataSource;
        ......
        <span class="hljs-keyword">return</span> CollectionUtils.computeIfAbsent(<span class="hljs-keyword">this</span>.dataSourceProxyMap, originalDataSource,
                BranchType.XA == dataSourceProxyMode ? DataSourceProxyXA::<span class="hljs-keyword">new</span> : DataSourceProxy::<span class="hljs-keyword">new</span>);
    }
}


<span class="hljs-comment">// CollectionUtils.java</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;K, V&gt; <span class="hljs-function">V <span class="hljs-title">computeIfAbsent</span><span class="hljs-params">(Map&lt;K, V&gt; map, K key, Function&lt;? <span class="hljs-keyword">super</span> K, ? extends V&gt; mappingFunction)</span> </span>{
    V value = map.get(key);
    <span class="hljs-keyword">if</span> (value != <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">return</span> value;
    }
    <span class="hljs-keyword">return</span> map.computeIfAbsent(key, mappingFunction);
}
</code></pre>
<h3>客户端数据源配置</h3>
<ol>
<li>配置了两个数据源：<code>DynamicDataSource</code>、<code>P6DataSource</code></li>
<li><code>P6DataSource</code>可以看成是对<code>DynamicDataSource</code>的一层包装</li>
<li>我们暂时不去管这个配置合不合理，现在只是单纯的基于这个数据源配置分析问题</li>
</ol>
<pre><code class="language-java"><span class="hljs-meta">@Qualifier</span>(<span class="hljs-string">"dsMaster"</span>)
<span class="hljs-meta">@Bean</span>(<span class="hljs-string">"dsMaster"</span>)
<span class="hljs-function">DynamicDataSource <span class="hljs-title">dsMaster</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DynamicDataSource(masterDsRoute);
}

<span class="hljs-meta">@Primary</span>
<span class="hljs-meta">@Qualifier</span>(<span class="hljs-string">"p6DataSource"</span>)
<span class="hljs-meta">@Bean</span>(<span class="hljs-string">"p6DataSource"</span>)
<span class="hljs-function">P6DataSource <span class="hljs-title">p6DataSource</span><span class="hljs-params">(@Qualifier(<span class="hljs-string">"dsMaster"</span>)</span> DataSource dataSource) </span>{
    P6DataSource p6DataSource =  <span class="hljs-keyword">new</span> P6DataSource(dsMaster());
    <span class="hljs-keyword">return</span> p6DataSource;
}
</code></pre>
<h3>分析过程</h3>
<p><code>假设现在大家都已经知道了 ConcurrentHashMap#computeIfAbsent 可能会产生的问题</code>，已知现在产生了这个问题，结合堆栈信息，我们可以知道大概哪里产生了这个问题。</p>
<p>1、<code>ConcurrentHashMap#computeIfAbsent</code>会产生这个问题的前提条件是：<code>两个key的hashcode相同</code>；<code>mappingFunction中对应了一个put操作</code>。结合我们seata的使用场景，mappingFunction对应的是<code>DataSourceProxy::new</code>，说明在DataSourceProxy的构造方法中可能会触发put操作</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00d8e13f71644c63b3bbb58c93b30e0c~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<pre><code class="language-java">执行AOP代理数据源相关方法 =&gt;
进入SeataAutoDataSourceProxyAdvice切面逻辑 =&gt; 
执行DataSourceProxyHolder#putDataSource方法 =&gt; 
执行DataSourceProxy::new =&gt; 
AOP代理数据源的getConnection方法 =&gt; 
原生数据源的getConnection方法  =&gt; 
进入SeataAutoDataSourceProxyAdvice切面逻辑 =&gt; 
执行DataSourceProxyHolder#putDataSource方法 =&gt; 
执行DataSourceProxy::new  =&gt; 
DuridDataSource的getConnection方法
</code></pre>
<p>2、步骤1中说的<code>AOP代理数据源</code>和<code>原生数据源</code>分别是什么？看下面这张图
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3579631f58df4d17bfcd6f28ccc3fd79~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<p>3、上面还说到了产生这个问题还有一个条件<code>两个key的hashcode相同</code>，但我看这两个数据源对象都没有重写<code>hashcode</code>方法，所以按理来说，这两个对象的hashcode一定是不同的。后面又再看了一遍ConcurrentHashMap这个问题，感觉<code>两个key的hashcode相同</code>这个说法是不对的，<code>两个key会产生hash冲突</code>更合理一些，这样就能解释两个hashcode不同的对象为啥会遇上这个问题了。为了证明这个，下面我给了一个例子</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Test</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        ConcurrentHashMap map = <span class="hljs-keyword">new</span> ConcurrentHashMap(<span class="hljs-number">8</span>);
        Num n1 = <span class="hljs-keyword">new</span> Num(<span class="hljs-number">3</span>);
        Num n2 = <span class="hljs-keyword">new</span> Num(<span class="hljs-number">19</span>);
        Num n3 = <span class="hljs-keyword">new</span> Num(<span class="hljs-number">20</span>);
    
<span class="hljs-comment">//      map.computeIfAbsent(n1, k1 -&gt; map.computeIfAbsent(n3, k2 -&gt; 200));      //  这行代码不会导致程序死循环</span>
        map.computeIfAbsent(n1, k1 -&gt; map.computeIfAbsent(n2, k2 -&gt; <span class="hljs-number">200</span>));      <span class="hljs-comment">// 这行代码会导致程序死循环</span>
    }

    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Num</span></span>{
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> i;
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Num</span><span class="hljs-params">(<span class="hljs-keyword">int</span> i)</span></span>{
            <span class="hljs-keyword">this</span>.i = i;
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">hashCode</span><span class="hljs-params">()</span> </span>{
            <span class="hljs-keyword">return</span> i;
        }
    }
}
</code></pre>
<ol>
<li>为了方便重现问题，我们重写了<code>Num#hashCode</code>方法，保证构造函数入参就是hashcode的值</li>
<li>创建一个ConcurrentHashMap对象，initialCapacity为8，sizeCtl计算出来的值为16，即该mao中数组长度默认为16</li>
<li>创建对象<code>n1</code>，入参为3，即hashcode为3，计算得出其对应的数组下标为3</li>
<li>创建对象<code>n2</code>，入参为19，即hashcode为19，计算得出其对应的数组下标为3，此时我们可以认为<code>n1和n2产生了hash冲突</code></li>
<li>创建对象<code>n3</code>，入参为20，即hashcode为20，计算得出其对应的数组下标为4</li>
<li>执行<code>map.computeIfAbsent(n1, k1 -&gt; map.computeIfAbsent(n3, k2 -&gt; 200))</code>，程序正常退出：<code>因为n1和n3没有hash冲突，所以正常结束</code></li>
<li>执行<code>map.computeIfAbsent(n1, k1 -&gt; map.computeIfAbsent(n2, k2 -&gt; 200))</code>，程序正常退出：<code>因为n1和n2产生了hash冲突，所以陷入死循环</code></li>
</ol>
<p>4、在对象初始化的时候，<code>SeataDataSourceBeanPostProcessor</code>不是已经将对象对应的数据源代理初始化好了吗？为什么在<code>SeataAutoDataSourceProxyAdvice</code>中还是会创建对应的数据源代理呢？</p>
<ol>
<li>首先，<code>SeataDataSourceBeanPostProcessor</code>执行时期是晚于AOP代理对象创建的，所以在执行<code>SeataDataSourceBeanPostProcessor</code>相关方法的时候，<code>SeataAutoDataSourceProxyAdvice</code>其实应生效了</li>
<li><code>SeataDataSourceBeanPostProcessor</code>中向map中添加元素时，key为<code>AOP代理数据源</code>；<code>SeataAutoDataSourceProxyAdvice</code>中的<code>invocation.getThis()</code>中拿到的是<code>原生数据源</code>，所以key不相同</li>
</ol>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/747b664a0b6c4f58843947576dd0856e~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<p>5、还有一个问题，<code>SeataAutoDataSourceProxyAdvic#invoke</code>方法中并没有过滤<code>toString、hashCode</code>等方法，cglib创建的代理对象默认会重写这几个方法，如果在向map中put元素的时候触发了代理对象的这些方法，此时又会重新进入<code>SeataAutoDataSourceProxyAdvic#invoke</code>切面，直到线程堆栈益处</p>
<h1>问题总结</h1>
<ol>
<li>在两个key会产生hash冲突的时候，会触发<code>ConcurrentHashMap#computeIfAbsent</code>BUG，该BUG的表现就是让当前线程陷入死循环</li>
<li>业务反馈，该问题是偶现的，偶现的原因有两种：首先，该应用是多节点部署，但线上只有一个节点触发了该BUG(hashcode冲突)，所以只有当请求打到这个节点的时候才有可能会触发该BUG；其次，因为每次重启对象地址(hashcode)都是不确定的，所以并不是每次应用重启之后都会触发，但如果一旦触发，该节点就会一直存在这个问题。有一个线程一直在死循环，并将其它尝试从map中获取代理数据源的线程阻塞了，这种现象在业务上的反馈就是请求卡住了。如果连续请求都是这样，此时业务方可能会重启服务，然后<code>因为重启后hash冲突不一定存在，可能重启后业务表现就正常了，但也有可能在下次重启的时候又会触发了这个BUG</code></li>
<li>当遇到这个问题时，从整个问题上来看，确实就是死锁了，因为那个死循环的线程占者锁一直不释放，导致其它操作该map的线程被BLOCK了</li>
<li>本质上还是因为<code>ConcurrentHashMap#computeIfAbsent方法可能会触发BUG</code>，而seata的使用场景刚好就触发了该BUG</li>
<li>下面这个demo其实就完整的模拟了线上出问题时的场景，如下：</li>
</ol>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Test</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{

        ConcurrentHashMap map = <span class="hljs-keyword">new</span> ConcurrentHashMap(<span class="hljs-number">8</span>);

        Num n1 = <span class="hljs-keyword">new</span> Num(<span class="hljs-number">3</span>);
        Num n2 = <span class="hljs-keyword">new</span> Num(<span class="hljs-number">19</span>);

        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i&lt; <span class="hljs-number">20</span>; i++){
            <span class="hljs-keyword">new</span> Thread(()-&gt; {
                <span class="hljs-keyword">try</span> {
                    Thread.sleep(<span class="hljs-number">1000</span>);
                } <span class="hljs-keyword">catch</span> (InterruptedException e) {
                    e.printStackTrace();
                }

                map.computeIfAbsent(n1, k-&gt; <span class="hljs-number">200</span>);
            }).start();
        }
        map.computeIfAbsent(n1, k1 -&gt; map.computeIfAbsent(n2, k2 -&gt; <span class="hljs-number">200</span>));
    }


    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Num</span></span>{
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> i;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Num</span><span class="hljs-params">(<span class="hljs-keyword">int</span> i)</span></span>{
            <span class="hljs-keyword">this</span>.i = i;
        }
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">hashCode</span><span class="hljs-params">()</span> </span>{
            <span class="hljs-keyword">return</span> i;
        }
    }
}
</code></pre>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d6134c6498fa49c4a68b2745ba0895e3~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<h3>解决问题</h3>
<p>可以从两方面解决这个问题：</p>
<ol>
<li>业务方改动：P6DataSource 和 DynamicDataSource 没必要都被代理，直接代理P6DataSource就可以了，DynamicDataSource没有必要声明成一个Bean；或者通过excluds属性排除P6DataSource，这样就不会存在重复代理问题</li>
<li>Seata完善：完善数据源代理相关逻辑</li>
</ol>
<h5>业务方改动</h5>
<p>1、数据源相关的配置改成如下即可：</p>
<pre><code class="language-java"><span class="hljs-meta">@Primary</span>
<span class="hljs-meta">@Qualifier</span>(<span class="hljs-string">"p6DataSource"</span>)
<span class="hljs-meta">@Bean</span>(<span class="hljs-string">"p6DataSource"</span>)
<span class="hljs-function">P6DataSource <span class="hljs-title">p6DataSource</span><span class="hljs-params">(@Qualifier(<span class="hljs-string">"dsMaster"</span>)</span> DataSource dataSource) </span>{
    P6DataSource p6DataSource =  <span class="hljs-keyword">new</span> P6DataSource(<span class="hljs-keyword">new</span> TuYaDynamicDataSource(masterDsRoute));
    logger.warn(<span class="hljs-string">"dsMaster={}, hashcode={}"</span>,p6DataSource, p6DataSource.hashCode());
    <span class="hljs-keyword">return</span> p6DataSource;
}
</code></pre>
<p>2、或者不改变目前的数据源配置，添加excluds属性</p>
<pre><code class="language-java"><span class="hljs-meta">@EnableAutoDataSourceProxy</span>(excludes={<span class="hljs-string">"P6DataSource"</span>})
</code></pre>
<h5>Seata完善</h5>
<p>1、<code>ConcurrentHashMap#computeIfAbsent</code>方法改成双重检查，如下：</p>
<pre><code class="language-java">SeataDataSourceProxy dsProxy = dataSourceProxyMap.get(originalDataSource);
<span class="hljs-keyword">if</span> (dsProxy == <span class="hljs-keyword">null</span>) {
    <span class="hljs-keyword">synchronized</span> (dataSourceProxyMap) {
        dsProxy = dataSourceProxyMap.get(originalDataSource);
        <span class="hljs-keyword">if</span> (dsProxy == <span class="hljs-keyword">null</span>) {
            dsProxy = createDsProxyByMode(dataSourceProxyMode, originalDataSource);
            dataSourceProxyMap.put(originalDataSource, dsProxy);
        }
    }
}
<span class="hljs-keyword">return</span> dsProxy;
</code></pre>
<p>之前我想直接改<code>CollectionUtils#computeIfAbsent</code>方法，群里大佬反馈这样可能会导致数据源多次创建，确实有这个问题：如下</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;K, V&gt; <span class="hljs-function">V <span class="hljs-title">computeIfAbsent</span><span class="hljs-params">(Map&lt;K, V&gt; map, K key, Function&lt;? <span class="hljs-keyword">super</span> K, ? extends V&gt; mappingFunction)</span> </span>{
    V value = map.get(key);
    <span class="hljs-keyword">if</span> (value != <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">return</span> value;
    }
    value = mappingFunction.apply(key);
    <span class="hljs-keyword">return</span> map.computeIfAbsent(key, value);
}
</code></pre>
<p>2、SeataAutoDataSourceProxyAdvice切面逻辑中添加一些过滤</p>
<pre><code class="language-java">Method m = BeanUtils.findDeclaredMethod(dataSourceProxyClazz, method.getName(), method.getParameterTypes());
<span class="hljs-keyword">if</span> (m != <span class="hljs-keyword">null</span> &amp;&amp; DataSource.class.isAssignableFrom(method.getDeclaringClass())) {
    SeataDataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis(), dataSourceProxyMode);
    <span class="hljs-keyword">return</span> m.invoke(dataSourceProxy, args);
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> invocation.proceed();
}
</code></pre>
<h3>遗留问题</h3>
<p>在<code>SeataDataSourceBeanPostProcessor</code>和<code>SeataAutoDataSourceProxyAdvice</code>对应方法中，向map中初始化<code>seata数据源代理</code>时对应的key根本就不同，<code>SeataDataSourceBeanPostProcessor</code>中对应的key是<code>AOP代理数据源</code>；<code>SeataAutoDataSourceProxyAdvice</code>中对应的key是原生对象，此时就造成了不必要的<code>seata数据源代理</code>对象的创建。</p>
<p>针对这个问题，大家有什么好的建议？能不能为<code>SeataDataSourceBeanPostProcessor</code>指定一个order，让其在创建AOP代理对象之前生效</p>
<h1>原文链接</h1>
<p><a href="https://juejin.cn/post/6939041336964153352/">https://juejin.cn/post/6939041336964153352/</a></p>
</section><footer class="footer-container"><div class="footer-body"><img src="//img.alicdn.com/tfs/TB1dGrSwVT7gK0jSZFpXXaTkpXa-4802-1285.png"/><p class="docsite-power">website powered by docsite</p><div class="cols-container"><div class="col col-12"><h3>愿景</h3><p>Seata 是一款阿里巴巴开源的分布式事务解决方案，致力于在微服务架构下提供高性能和简单易用的分布式事务服务。</p></div><div class="col col-6"><dl><dt>文档</dt><dd><a href="/zh-cn/docs/overview/what-is-seata.html" target="_self">Seata 是什么？</a></dd><dd><a href="/zh-cn/docs/user/quickstart.html" target="_self">快速开始</a></dd><dd><a href="https://github.com/seata/seata.github.io/issues/new" target="_self">报告文档问题</a></dd><dd><a href="https://github.com/seata/seata.github.io" target="_self">在Github上编辑此文档</a></dd></dl></div><div class="col col-6"><dl><dt>资源</dt><dd><a href="/zh-cn/blog/index.html" target="_self">博客</a></dd><dd><a href="/zh-cn/community/index.html" target="_self">社区</a></dd></dl></div></div><div class="copyright"><span>Copyright © 2019 Seata</span></div></div></footer></div></div>
	<script src="https://f.alicdn.com/react/15.4.1/react-with-addons.min.js"></script>
	<script src="https://f.alicdn.com/react/15.4.1/react-dom.min.js"></script>
	<script>
		window.rootPath = '';
  </script>
	<script src="/build/blogDetail.js"></script>
	<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?104e73ef0c18b416b27abb23757ed8ee";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
    </script>
</body>
</html>
